#include "load.thread.h"

#include "tracer.h"
#include "session.h"

#include <QtCore/QtCore>
#include <fstream>
#include <mutex>
#include <unordered_set>
#include <unordered_map>
#include <condition_variable>
#include <memory>

#include "stream.h"
#include "cache.thread.h"
#include "image.reader.h"
#include "annot.tree.h"
#include "misc.h"

struct CubeData {

	enum class State {
		Empty=0,
		Reading,
		Ready,
		Error
	};

	CubeId id;

	LoadThreadShared* lts;
	int refc;
	std::vector<State> states;

	int64_t xsize, ysize, zsize;
	int64_t widthAdj, heightAdj, depthAdj;

	char* data;
	CubeType type;

	int32_t x1() const { return id.x0+xsize*id.nx; }
	int32_t y1() const { return id.y0+ysize*id.ny; }
	int32_t z1() const { return id.z0+zsize*id.nz; }

	int nFinished;
	int nError;

	bool finished() const { return nFinished==id.nx*id.ny*id.nz; }

	CubeData(LoadThreadShared* _lts, const CubeId& _id):
		id(_id), lts{_lts}, refc{0}, states(id.size(), State::Empty),
		xsize{0}, ysize{0}, zsize{0},
		widthAdj{0}, heightAdj{0}, depthAdj{0},
		data{nullptr}, type{CubeType::Unknown},
		nFinished{0}, nError{0}
	{
	}
	~CubeData() {
		if(data)
			delete[] data;
	}

};

struct Location {
	int id;
	bool isPat;
	bool isUrl;
	QString location;
};

struct Source {
	QString name;
	Location* location; //*
	AnnotTree* annotTree; //*
	std::array<int64_t, 3> size;
	std::array<int64_t, 3> cubesize;
	CubeXform xform;
};

int bytes_per_voxel(CubeType type) {
	switch(type) {
		case CubeType::U8:
		case CubeType::I8:
			return 1;
		case CubeType::U16:
		case CubeType::I16:
			return 2;
		case CubeType::U32:
		case CubeType::I32:
			return 4;
		case CubeType::U64:
		case CubeType::I64:
			return 8;
		case CubeType::F32:
			return 4;
		case CubeType::F64:
			return 8;
		default:
			return 0;
	}
}

struct LoadThreadShared {

	std::unordered_map<std::pair<bool, QString>, Location*> locations;
	std::mutex mtx_loc;

	std::unordered_set<CubeData*> cubes;
	std::mutex mtx_cubes;
	std::condition_variable cv_cubes;


	std::unordered_map<QString, AnnotTree*> annotTrees;
	std::mutex mtx_annots;
	std::condition_variable cv_annots;

	LoadThreadShared():
		locations{}, mtx_loc{},
		cubes{}, mtx_cubes{}, cv_cubes{},
		annotTrees{}, mtx_annots{}, cv_annots{}
	{
	}
	~LoadThreadShared() {
		if(!cubes.empty())
			printMessage("Cube data memory leak!");
		for(auto& p: locations)
			delete p.second;
		for(auto& p: annotTrees)
			delete p.second;
	}

	Location* getLocation(bool isPat, bool isUrl, const QString& loc) {
		if(loc.isEmpty())
			return nullptr;
		std::unique_lock<std::mutex> lck{mtx_loc};
		auto i=locations.find({isPat, loc});
		if(i!=locations.end())
			return i->second;
		int id=locations.size();
		return locations[{isPat, loc}]=new Location{id, isPat, isUrl, loc};
	}

	AnnotTree* getAnnot(const QString& loc) {
		if(loc.isEmpty())
			return nullptr;
		std::unique_lock<std::mutex> lck{mtx_annots};
		auto i=annotTrees.find(loc);
		if(i!=annotTrees.end())
			return i->second;
		int id=annotTrees.size();
		return annotTrees[loc]=new AnnotTree{id, loc};
	}

	CubeDataRef getCube(const CubeId& id) {
		std::unique_lock<std::mutex> lck{mtx_cubes};
		for(auto cube: cubes) {
			if(cube->id==id) {
				cube->refc++;
				return CubeDataRef{cube};
			}
		}
		auto cube=new CubeData{this, id};
		cube->refc++;
		cubes.insert(cube);
		return CubeDataRef{cube};
	}
	void ref(CubeData* c) {
		std::unique_lock<std::mutex> lck{mtx_cubes};
		c->refc++;
	}
	void unref(CubeData* c) {
		std::unique_lock<std::mutex> lck{mtx_cubes};
		c->refc--;
		if(c->refc<=0) {
			cubes.erase(c);
			delete c;
		}
	}
	CubeData::State changeState(CubeData* c, int i, CubeData::State prev, CubeData::State s) {
		std::unique_lock<std::mutex> lck{mtx_cubes};
		if(c->states[i]==prev) {
			c->states[i]=s;
			if(prev==CubeData::State::Reading && (s==CubeData::State::Ready || s==CubeData::State::Error))
				cv_cubes.notify_all();
			return prev;
		} else {
			return c->states[i];
		}
	}
	bool alloc(CubeData* c, CubeType type, int64_t w, int64_t h, int64_t d) {
		std::unique_lock<std::mutex> lck{mtx_cubes};
		if(c->data)
			return true;
		try {
			c->xsize=w;
			c->ysize=h;
			c->zsize=d;
			c->type=type;
			c->widthAdj=(c->id.nx*c->xsize+7)/8*8;
			c->heightAdj=c->id.ny*c->ysize;
			c->depthAdj=c->id.nz*c->zsize;
			c->data=new char[bytes_per_voxel(type)*c->widthAdj*c->heightAdj*c->depthAdj];
		} catch(...) {
			delete c->data;
			return false;
		}
		return true;
	}
	void waitForCube(CubeData* c, int i) {
		std::unique_lock<std::mutex> lck{mtx_cubes};
		while(c->states[i]==CubeData::State::Reading)
			cv_cubes.wait(lck);
	}
};

LoadThreadShared* LoadThread::createShared() {
	return new LoadThreadShared{};
}
void LoadThread::destroyShared(LoadThreadShared* lts) {
	delete lts;
}

CubeDataRef::CubeDataRef(CubeData* f):
	_cube{f}
{
}
CubeDataRef::CubeDataRef(const CubeDataRef& r):
	_cube{r._cube}
{
	if(_cube) {
		_cube->lts->ref(_cube);
	}
}
CubeDataRef& CubeDataRef::operator=(const CubeDataRef& r) {
	if(_cube) {
		_cube->lts->unref(_cube);
	}
	_cube=r._cube;
	if(_cube) {
		_cube->lts->ref(_cube);
	}
	return *this;
}
void CubeDataRef::release() {
	if(_cube) {
		_cube->lts->unref(_cube);
		_cube=nullptr;
	}
}
CubeType CubeDataRef::type() const {
	return _cube->type;
}
int64_t CubeDataRef::width() const {
	return _cube->id.nx*_cube->xsize;
}
int64_t CubeDataRef::height() const {
	return _cube->id.ny*_cube->ysize;
}
int64_t CubeDataRef::depth() const {
	return _cube->id.nz*_cube->zsize;
}
int64_t CubeDataRef::widthAdj() const {
	return _cube->widthAdj;
}
int64_t CubeDataRef::heightAdj() const {
	return _cube->heightAdj;
}
int64_t CubeDataRef::depthAdj() const {
	return _cube->depthAdj;
}
int64_t CubeDataRef::size(int dir) const {
	switch(dir) {
		case 0:
			return width();
		case 1:
			return height();
		case 2:
			return depth();
	}
	return 0;
}
const char* CubeDataRef::data() const {
	return _cube->data;
}
const CubeId& CubeDataRef::cubeId() const {
	return _cube->id;
}


template<typename T, size_t N> bool parseValues(const std::string& s, std::array<T, N>& v);
template<bool T> QString compose(const QString& pos, const QString& path);

struct LoadPosition {
	int idx;
	int32_t x0, y0, z0;
	int xn, yn, zn;
	bool operator==(const LoadPosition& p) const {
		if(idx!=p.idx) return false;
		if(x0!=p.x0) return false;
		if(y0!=p.y0) return false;
		if(z0!=p.z0) return false;
		if(xn!=p.xn) return false;
		if(yn!=p.yn) return false;
		if(zn!=p.zn) return false;
		return true;
	}
	int nFiles() const {
		return xn*yn*zn;
	}
};
namespace std {
	template<>
		struct hash<LoadPosition> {
			size_t operator()(const LoadPosition& pos) const {
				size_t h=0x12345678;
				h=h^pos.idx; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				h=h^pos.x0; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				h=h^pos.y0; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				h=h^pos.z0; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				h=h^pos.xn; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				h=h^pos.yn; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				h=h^pos.zn; h=(h>>(sizeof(h)*8/2))|(h<<(sizeof(h)*8/2));
				return h;
			}
		};
}
struct CubePointer {
	int i;
	int xi, yi, zi;
};
struct LoadData {
	LoadPosition pos;
	CubeDataRef cube; // *
	std::vector<QString> files{};
	std::vector<CubePointer> ptrs{};
	std::vector<CacheFileRef> cache{}; // *
};

struct LoadThreadPriv {
	Session* session;
	LoadThread* thread;

	LoadThreadShared* loaderShared;

	QVector<LoadThreadInput> rootlocs;
	QSet<QString> pathsVisited;
	QVector<Source> sources;
	bool sources_ready;

	std::mutex mtx_in;
	std::condition_variable cv_in;
	bool m_stopRequested;
	std::unordered_map<int, LoadPosition> m_toload;

	std::mutex mtx_out;
	std::vector<std::pair<int, Cube>> cubes_out;

	int nFilesToLoad;
	int nFilesLoaded;

	// XXX
	~LoadThreadPriv() {
		rootlocs.clear();
		sources.clear();
	}

	template<bool T> AnnotTree* parseAnnot(const std::map<std::string, std::string>& data, const QString& pos) {
		auto li=data.find("annotation");
		if(li!=data.end()) {
			QString loc=QString::fromStdString(li->second);
			if(isUrl(loc)) {
				return loaderShared->getAnnot(loc);
			} else {
				auto pos_new=compose<T>(pos, loc);
				return loaderShared->getAnnot(pos_new);
			}
		}
		return nullptr;
	}
	template<bool T> void addSource(const std::map<std::string, std::string>& data, const QString& pfx, const QString& pos, const std::array<double, 3>& p0, const std::array<double, 9>& dir) {
		//for(int i=0; i<9; i++)
		//std::cerr<<dir[i]<<"\n";
		CubeXform xform;
		auto p0i=data.find("origin");
		if(p0i==data.end()) {
			xform.origin=p0;
		} else {
			std::array<double, 3> p0_temp;
			auto r=parseValues<double, 3>(p0i->second, p0_temp);
			if(!r)
				throwError("Failed to parse origin.");
			for(int i=0; i<3; i++)
				xform.origin[i]=p0[i]+dir[0+i]*p0_temp[0]+dir[3+i]*p0_temp[1]+dir[6+i]*p0_temp[2];
		}
		auto diri=data.find("direction");
		if(diri==data.end()) {
			xform.direction=dir;
		} else {
			std::array<double, 9> dir_temp;
			auto r=parseValues<double, 9>(diri->second, dir_temp);
			if(!r)
				throwError("Failed to parse direction.");
			for(int i=0; i<3; i++)
				for(int j=0; j<3; j++)
					xform.direction[i+j*3]=dir[i+0*3]*dir_temp[0+j*3]+dir[i+1*3]*dir_temp[1+j*3]+dir[i+2*3]*dir_temp[2+j*3];
		}

		auto ri=data.find("link");
		if(ri!=data.end()) {
			QString link=QString::fromStdString(ri->second);
			if(link.compare(QLatin1String{"catalog"}, Qt::CaseInsensitive)==0)
				throwError("A link to the same catalog not allowed.");
			if(!link.endsWith(QLatin1String{"/catalog"}, Qt::CaseInsensitive))
				throwError("A link must refer to a catalog file.");
			if(isUrl(link)) {
				loadCatalog<true>(pfx+'/', link, xform.origin, xform.direction);
			} else {
				auto pos_new=compose<T>(pos, link);
				loadCatalog<T>(pfx+'/', pos_new, xform.origin, xform.direction);
			}
			return;
		}
		auto li=data.find("location");
		if(li!=data.end()) {
			QString loc=QString::fromStdString(li->second);
			if(!loc.endsWith(QLatin1String{".nrrd"}, Qt::CaseInsensitive)
#ifdef WITH_VP9
					&& !loc.endsWith(QLatin1String{".webm"}, Qt::CaseInsensitive)
#endif
#ifdef WITH_HEVC
					&& !loc.endsWith(QLatin1String{".hevc"}, Qt::CaseInsensitive)
#endif
					&& !loc.endsWith(QLatin1String{".tiff"}, Qt::CaseInsensitive)
					&& !loc.endsWith(QLatin1String{".tif"}, Qt::CaseInsensitive))
				throwError("Cannot handle file type: ", loc);

			auto annotTree=parseAnnot<T>(data, pos);

			Location* location{nullptr};
			if(isUrl(loc)) {
				location=loaderShared->getLocation(false, true, loc);
			} else {
				auto pos_new=compose<T>(pos, loc);
				location=loaderShared->getLocation(false, T, pos_new);
			}

			if(location)
				sources.push_back(Source{pfx, location, annotTree, {0, 0, 0}, {0, 0, 0}, xform});
			return;
		}
		auto pi=data.find("pattern");
		if(pi!=data.end()) {
			QString pat=QString::fromStdString(pi->second);
			if(!pat.endsWith(QLatin1String{".nrrd"}, Qt::CaseInsensitive)
#ifdef WITH_VP9
					&& !pat.endsWith(QLatin1String{".webm"}, Qt::CaseInsensitive)
#endif
#ifdef WITH_HEVC
					&& !pat.endsWith(QLatin1String{".hevc"}, Qt::CaseInsensitive)
#endif
					&& !pat.endsWith(QLatin1String{".tiff"}, Qt::CaseInsensitive)
					&& !pat.endsWith(QLatin1String{".tif"}, Qt::CaseInsensitive))
				throwError("Cannot handle file type: ", pat);
			std::array<int64_t, 3> s;
			std::array<int64_t, 3> cs;
			auto si=data.find("size");
			if(si==data.end())
				throwError("Volume size not defined.");
			auto r=parseValues<int64_t, 3>(si->second, s);
			if(!r)
				throwError("Failed to parse volume size.");
			auto ci=data.find("cubesize");
			if(ci==data.end())
				throwError("Cube size not defined.");
			r=parseValues<int64_t, 3>(ci->second, cs);
			if(!r)
				throwError("Failed to parse cube size.");

			auto annotTree=parseAnnot<T>(data, pos);

			Location* location{nullptr};
			if(isUrl(pat)) {
				location=loaderShared->getLocation(true, true, pat);
			} else {
				auto pos_new=compose<T>(pos, pat);
				location=loaderShared->getLocation(true, T, pos_new);
			}

			if(location)
				sources.push_back(Source{pfx, location, annotTree, s, cs, xform});
			return;
		}
		throwError("Unknown type of image source.");
	}
	template<bool T> void parseCatalog(std::istream& ifs, const QString& pfx, const QString& pos, const std::array<double, 3>& p0, const std::array<double, 9>& dir) {
		std::string line;
		QString section{};
		std::map<std::string, std::string> data{};
		while(std::getline(ifs, line)) {
			if(line.empty())
				continue;
			if(line[0]=='#')
				continue;
			if(line[0]=='[') {
				auto i=line.find(']');
				if(i==std::string::npos)
					throwError("Failed to parse line: ", line);
				if(!section.isNull()) {
					addSource<T>(data, pfx+section, pos, p0, dir);
					data.clear();
				}
				section=QLatin1String(&line[1], i-1);
			} else {
				auto i=line.find('=');
				if(i==std::string::npos)
					throwError("Failed to parse line: ", line);
				// XXX tolower
				data[line.substr(0, i)]=line.substr(i+1);
			}
		}
		if(!ifs.eof())
			throwError("Error while reading catalog file.");
		if(!section.isNull()) {
			addSource<T>(data, pfx+section, pos, p0, dir);
		}
	}
	template<bool U> void loadCatalog(const QString& pfx, const QString& pos, const std::array<double, 3>& p0, const std::array<double, 9>& dir) {
		auto i=pathsVisited.find(pos);
		if(i!=pathsVisited.end())
			return;
		pathsVisited.insert(pos);

		if(U) {
			auto f=CacheThread::instance()->download(pos);
			if(!f)
				throwError("Failed to start downloading catalog.");
			auto r=CacheThread::instance()->waitForFile(f);
			if(r!=0)
				throwError("Failed to download catalog. (r!=0)");
			if(!f.isReady())
				throwError("Failed to download catalog. (not ready)");
			InputStream mems{};
			mems.open(f.buffers(), f.size());
			parseCatalog<U>(mems, pfx, pos, p0, dir);
		} else {
			std::ifstream ifs{pos.toStdString()};
			parseCatalog<U>(ifs, pfx, pos, p0, dir);
		}
	}

	bool setPositionSmall(int ch, int idx, const Cube& cur) {
		if(idx==0)
			return false;
		printMessage("setpossmall");
		bool load_new_cube=true;
		if(cur.data) {
			auto& source=sources[idx];
			if(cur.data->id.location==source.location
					&& cur.data->id.annotTree==source.annotTree) {
				if(cur.xform==source.xform)
					load_new_cube=false;
			}
		}
		if(load_new_cube) {
			std::unique_lock<std::mutex> lck_in{mtx_in};
			auto it=m_toload.find(ch);
			if(it==m_toload.end()) {
				m_toload[ch]={idx, 0, 0, 0, 1, 1, 1};
				nFilesToLoad+=m_toload[ch].nFiles();
			} else {
				nFilesToLoad-=it->second.nFiles();
				it->second={idx, 0, 0, 0, 1, 1, 1};
				nFilesToLoad+=it->second.nFiles();
			}
			cv_in.notify_one();
		}
		return load_new_cube;
	}
	bool setPositionBig(int ch, int idx, double x, double y, double z, double sight, const Cube& cur) {
		if(idx==0)
			return false;
		printMessage("setposbig");
		bool load_new_cube=true;
		bool cube_reusable=false;
		auto& source=sources[idx];
		printMessage(x, '/', y, '/', z);
		printMessage(source.xform.direction[0], '/', source.xform.direction[1], '/', source.xform.direction[2]);
		printMessage(source.xform.direction_inv[0], '/', source.xform.direction_inv[1], '/', source.xform.direction_inv[2]);
		double dx=x-source.xform.origin[0];
		double dy=y-source.xform.origin[1];
		double dz=z-source.xform.origin[2];
		double xp=source.xform.direction_inv[0]*dx+source.xform.direction_inv[3]*dy+source.xform.direction_inv[6]*dz;
		double yp=source.xform.direction_inv[1]*dx+source.xform.direction_inv[4]*dy+source.xform.direction_inv[7]*dz;
		double zp=source.xform.direction_inv[2]*dx+source.xform.direction_inv[5]*dy+source.xform.direction_inv[8]*dz;
		printMessage(xp, '/', yp, '/', zp);
		bool same_channel=false;
		if(cur.data) {
			if(cur.data->id.location==source.location
					&& cur.data->id.annotTree==source.annotTree) {
				if(cur.xform==source.xform) {
					cube_reusable=true;
					if(xp-sight/source.xform.resolution[0]-cur.data->id.x0<0) cube_reusable=false;
					if(xp+sight/source.xform.resolution[0]-cur.data->x1()>0) cube_reusable=false;
					if(yp-sight/source.xform.resolution[1]-cur.data->id.y0<0) cube_reusable=false;
					if(yp+sight/source.xform.resolution[1]-cur.data->y1()>0) cube_reusable=false;
					if(zp-sight/source.xform.resolution[2]-cur.data->id.z0<0) cube_reusable=false;
					if(zp+sight/source.xform.resolution[2]-cur.data->z1()>0) cube_reusable=false;
					printMessage(cube_reusable);
					same_channel=true;
				}
			}
		}
		if(load_new_cube) {
			double rr=sight*1.61803398875;
			int xn=rr/source.xform.resolution[0]*2/source.cubesize[0]+2;
			int yn=rr/source.xform.resolution[1]*2/source.cubesize[1]+2;
			int zn=rr/source.xform.resolution[2]*2/source.cubesize[2]+2;
			if(xn>(source.size[0]+source.cubesize[0]-1)/source.cubesize[0])
				xn=(source.size[0]+source.cubesize[0]-1)/source.cubesize[0];
			if(yn>(source.size[1]+source.cubesize[1]-1)/source.cubesize[1])
				yn=(source.size[1]+source.cubesize[1]-1)/source.cubesize[1];
			if(zn>(source.size[2]+source.cubesize[2]-1)/source.cubesize[2])
				zn=(source.size[2]+source.cubesize[2]-1)/source.cubesize[2];
			int32_t x0=xp;
			x0=((x0*2+source.cubesize[0])/source.cubesize[0]-xn)/2*source.cubesize[0];
			int32_t y0=yp;
			y0=((y0*2+source.cubesize[1])/source.cubesize[1]-yn)/2*source.cubesize[1];
			int32_t z0=zp;
			z0=((z0*2+source.cubesize[2])/source.cubesize[2]-zn)/2*source.cubesize[2];
			if(x0<0) x0=0;
			if(x0+source.cubesize[0]*xn-source.cubesize[0]>=source.size[0]) x0=(source.size[0]+source.cubesize[0]-1)/source.cubesize[0]*source.cubesize[0]-xn*source.cubesize[0];
			if(y0<0) y0=0;
			if(y0+source.cubesize[1]*yn-source.cubesize[1]>=source.size[1]) y0=(source.size[1]+source.cubesize[1]-1)/source.cubesize[1]*source.cubesize[1]-yn*source.cubesize[1];
			if(z0<0) z0=0;
			if(z0+source.cubesize[2]*zn-source.cubesize[2]>=source.size[2]) z0=(source.size[2]+source.cubesize[2]-1)/source.cubesize[2]*source.cubesize[2]-zn*source.cubesize[2];
			if(same_channel) {
				if(xn==cur.data->id.nx
						&& yn==cur.data->id.ny
						&& zn==cur.data->id.nz) {
					if(cube_reusable) {
						load_new_cube=false;
					}
					if(x0==cur.data->id.x0
							&& y0==cur.data->id.y0
							&& z0==cur.data->id.z0) {
						load_new_cube=false;
					}
				}
			}
			if(load_new_cube) {
				std::unique_lock<std::mutex> lck_in{mtx_in};
				auto it=m_toload.find(ch);
				if(it==m_toload.end()) {
					m_toload[ch]={idx, x0, y0, z0, xn, yn, zn};
					nFilesToLoad+=m_toload[ch].nFiles();
				} else {
					nFilesToLoad-=it->second.nFiles();
					it->second={idx, x0, y0, z0, xn, yn, zn};
					nFilesToLoad+=it->second.nFiles();
				}
				cv_in.notify_one();
			}
		}
		return load_new_cube;
	}
	bool setPosition(int ch, int idx, double x, double y, double z, double sight, const Cube& cur) {
		//if(ch<0 || ch>=9)
		//throwError("Channel index out of range!");
		if(idx<0 || idx>=sources.size())
			throwError("Mapped channel index out of range!");
		auto& source=sources[idx];
		if(source.location->isPat)
			return setPositionBig(ch, idx, x, y, z, sight, cur);
		return setPositionSmall(ch, idx, cur);
	}

	std::unique_ptr<LoadData> setupJobLocalSmall(const LoadPosition& pos) {
		if(pos.xn!=1 || pos.yn!=1 || pos.zn!=1)
			throwError("Asertion failed!");
		std::unique_ptr<LoadData> data{new LoadData{}};
		data->pos=pos;
		data->cube=loaderShared->getCube(CubeId{sources[pos.idx].location, sources[pos.idx].annotTree, pos.x0, pos.y0, pos.z0, pos.xn, pos.yn, pos.zn});
		std::unique_lock<std::mutex> lck{loaderShared->mtx_cubes};
		auto s=data->cube->states[0]; //none //reading //read //failed
		if(s==CubeData::State::Empty) {
			data->files.push_back(sources[pos.idx].location->location);
			data->ptrs.push_back({0});
		}
		return data;
	}
	std::unique_ptr<LoadData> setupJobLocalBig(const LoadPosition& pos) {
		std::unique_ptr<LoadData> data{new LoadData{}};
		data->pos=pos;
		data->cube=loaderShared->getCube(CubeId{sources[pos.idx].location, sources[pos.idx].annotTree, pos.x0, pos.y0, pos.z0, pos.xn, pos.yn, pos.zn});
		std::unique_lock<std::mutex> lck{loaderShared->mtx_cubes};
		int i=0;
		for(int zi=0; zi<pos.zn; zi++) {
			for(int yi=0; yi<pos.yn; yi++) {
				for(int xi=0; xi<pos.xn; xi++) {
					auto s=data->cube->states[i]; //none //reading //read //failed
					if(s==CubeData::State::Empty) {
						auto loc=getCubeFileName(sources[pos.idx].location->location, pos.x0+xi*sources[pos.idx].cubesize[0], pos.y0+yi*sources[pos.idx].cubesize[1], pos.z0+zi*sources[pos.idx].cubesize[2]);
						data->files.push_back(loc);
						data->ptrs.push_back({i, xi, yi, zi});
					}
					i++;
				}
			}
		}

		return data;
	}
	std::unique_ptr<LoadData> setupJobRemoteSmall(const LoadPosition& pos) {
		if(pos.xn!=1 || pos.yn!=1 || pos.zn!=1)
			throwError("Asertion failed!");
		std::unique_ptr<LoadData> data{new LoadData{}};
		data->pos=pos;
		data->cube=loaderShared->getCube(CubeId{sources[pos.idx].location, sources[pos.idx].annotTree, pos.x0, pos.y0, pos.z0, pos.xn, pos.yn, pos.zn});
		std::unique_lock<std::mutex> lck{loaderShared->mtx_cubes};
		auto s=data->cube->states[0]; //none //reading //read //failed
		if(s==CubeData::State::Empty) {
			data->files.push_back(sources[pos.idx].location->location);
			data->cache.push_back(CacheThread::instance()->download(sources[pos.idx].location->location));
			data->ptrs.push_back({0});
		}
		return data;
	}
	std::unique_ptr<LoadData> setupJobRemoteBig(const LoadPosition& pos) {
		std::unique_ptr<LoadData> data{new LoadData{}};
		data->pos=pos;
		data->cube=loaderShared->getCube(CubeId{sources[pos.idx].location, sources[pos.idx].annotTree, pos.x0, pos.y0, pos.z0, pos.xn, pos.yn, pos.zn});
		std::unique_lock<std::mutex> lck{loaderShared->mtx_cubes};
		int i=0;
		for(int zi=0; zi<pos.zn; zi++) {
			for(int yi=0; yi<pos.yn; yi++) {
				for(int xi=0; xi<pos.xn; xi++) {
					auto s=data->cube->states[i]; //none //reading //read //failed
					if(s==CubeData::State::Empty) {
						auto loc=getCubeFileName(sources[pos.idx].location->location, pos.x0+xi*sources[pos.idx].cubesize[0], pos.y0+yi*sources[pos.idx].cubesize[1], pos.z0+zi*sources[pos.idx].cubesize[2]);
						data->files.push_back(loc);
						data->cache.push_back(CacheThread::instance()->download(loc));
						data->ptrs.push_back({i, xi, yi, zi});
					}
					i++;
				}
			}
		}

		return data;
	}
	void loadImageSmall(ImageReader* imageReader, const QString& file, CubeData* cube) {
		if(!imageReader)
			throwError("Cannot read file: ", file);
		CubeType type;
		if(!imageReader->getType(&type))
			throwError("Cannot get image type: ", file);
		switch(type) {
			case CubeType::U8:
			case CubeType::U16:
			case CubeType::U32:
			case CubeType::I8:
			case CubeType::I16:
			case CubeType::I32:
			case CubeType::F32:
				break;
			default:
				throwError("Voxel type not supported");
		}
		auto at=cube->id.annotTree;
		int64_t w, h, d;
		if(!imageReader->getSizes(&w, &h, &d))
			throwError("Cannot get image sizes: ", file);
		if(at) {
			CubeType otype;
			auto atsize=at->size();
			if(atsize<=255) {
				otype=CubeType::U8;
			} else if(atsize<=65535) {
				otype=CubeType::U16;
			} else {
				otype=CubeType::U32;
			}

			if(!loaderShared->alloc(cube, otype, w, h, d))
				throwError("Failed to alloc cube memory");

			auto bpv=bytes_per_voxel(type);
			auto dat=new char[bpv*w*h*d];
			if(!imageReader->readData(dat, 0, 0, 0, bpv*w, bpv*w*h))
				throwError("Failed to read image data: ", file);

			switch(type) {
				case CubeType::U8:
					at->mapData(reinterpret_cast<uint8_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I8:
					at->mapData(reinterpret_cast<int8_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::U16:
					at->mapData(reinterpret_cast<uint16_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I16:
					at->mapData(reinterpret_cast<int16_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::U32:
					at->mapData(reinterpret_cast<uint32_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I32:
					at->mapData(reinterpret_cast<int32_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::U64:
					at->mapData(reinterpret_cast<uint64_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I64:
					at->mapData(reinterpret_cast<int64_t*>(dat), cube->data, w, h, d, 0, 0, 0, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				default:
					throwError("Voxel type not supported");
			}
		} else {
			if(!loaderShared->alloc(cube, type, w, h, d))
				throwError("Failed to alloc cube memory");
			auto bpv=bytes_per_voxel(type);
			if(!imageReader->readData(cube->data, 0, 0, 0, bpv*cube->widthAdj, bpv*cube->widthAdj*cube->heightAdj))
				throwError("Failed to read image data: ", file);
		}
	}
	void loadImageBig(ImageReader* imageReader, const QString& file, CubeData* cube, int xi, int yi, int zi, int64_t cw, int64_t ch, int64_t cd) {
		if(!imageReader)
			throwError("Cannot read file: ", file);
		CubeType type;
		if(!imageReader->getType(&type))
			throwError("Cannot get image type: ", file);
		switch(type) {
			case CubeType::U8:
			case CubeType::U16:
			case CubeType::U32:
			case CubeType::I8:
			case CubeType::I16:
			case CubeType::I32:
			case CubeType::F32:
				break;
			default:
				throwError("Voxel type not supported");
		}
		auto at=cube->id.annotTree;
		if(at) {
			CubeType otype;
			auto atsize=at->size();
			if(atsize<=255) {
				otype=CubeType::U8;
			} else if(atsize<=65535) {
				otype=CubeType::U16;
			} else {
				otype=CubeType::U32;
			}

			if(!loaderShared->alloc(cube, otype, cw, ch, cd))
				throwError("Failed to alloc cube memory");

			auto bpv=bytes_per_voxel(type);
			auto dat=new char[bpv*cw*ch*cd];
			if(!imageReader->readData(dat, 0, 0, 0, bpv*cw, bpv*cw*ch))
				throwError("Failed to read image data: ", file);

			switch(type) {
				case CubeType::U8:
					at->mapData(reinterpret_cast<uint8_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I8:
					at->mapData(reinterpret_cast<int8_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::U16:
					at->mapData(reinterpret_cast<uint16_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I16:
					at->mapData(reinterpret_cast<int16_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::U32:
					at->mapData(reinterpret_cast<uint32_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I32:
					at->mapData(reinterpret_cast<int32_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::U64:
					at->mapData(reinterpret_cast<uint64_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				case CubeType::I64:
					at->mapData(reinterpret_cast<int64_t*>(dat), cube->data, cw, ch, cd, xi*cw, yi*ch, zi*cd, cube->widthAdj, cube->widthAdj*cube->heightAdj);
					break;
				default:
					throwError("Voxel type not supported");
			}
		} else {
			if(!loaderShared->alloc(cube, type, cw, ch, cd))
				throwError("Failed to alloc cube memory");
			auto bpv=bytes_per_voxel(type);
			if(!imageReader->readData(cube->data, xi*cw, yi*ch, zi*cd, bpv*cube->widthAdj, bpv*cube->widthAdj*cube->heightAdj))
				throwError("Failed to read image data: ", file);
		}
	}
	bool loadCubeLocalSmall(LoadData* curJob) {
		if(curJob->files.empty())
			return false;
		size_t r=SIZE_MAX;
		if(!curJob->files[0].isEmpty())
			r=0;
		if(r>=curJob->files.size())
			return false;
		auto file=std::move(curJob->files[r]);
		auto s=loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Empty, CubeData::State::Reading);
		if(s==CubeData::State::Empty) {
			try {
				std::unique_ptr<ImageReader> imageReader{ImageReader::create(file.toStdString())};
				loadImageSmall(imageReader.get(), file, curJob->cube._cube);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Ready);
				curJob->cube->nFinished++;
			} catch(std::exception& e) {
				printMessage("Failed to load image: ", curJob->files[r]);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Error);
				curJob->cube->nFinished++;
				curJob->cube->nError++;
			}
			return true;
		} else if(s==CubeData::State::Reading) {
			loaderShared->waitForCube(curJob->cube._cube, 0);
			return true;
		} else {
			return true;
		}
	}
	bool loadCubeLocalBig(LoadData* curJob) {
		size_t r=SIZE_MAX;
		for(size_t i=0; i<curJob->files.size(); i++) {
			if(!curJob->files[i].isEmpty()) {
				r=i;
				break;
			}
		}
		if(r>=curJob->files.size())
			return false;
		auto file=std::move(curJob->files[r]);
		auto cpi=curJob->ptrs[r].i;
		auto s=loaderShared->changeState(curJob->cube._cube, cpi, CubeData::State::Empty, CubeData::State::Reading);
		if(s==CubeData::State::Empty) {
			try {
				std::unique_ptr<ImageReader> imageReader{ImageReader::create(file.toStdString())};
				loadImageBig(imageReader.get(), file, curJob->cube._cube, curJob->ptrs[r].xi, curJob->ptrs[r].yi, curJob->ptrs[r].zi, sources[curJob->pos.idx].cubesize[0], sources[curJob->pos.idx].cubesize[1], sources[curJob->pos.idx].cubesize[2]);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Ready);
				curJob->cube->nFinished++;
			} catch(std::exception& e) {
				printMessage("Failed to load image: ", curJob->files[r]);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Error);
				curJob->cube->nFinished++;
				curJob->cube->nError++;
			}
			return true;
		} else if(s==CubeData::State::Reading) {
			loaderShared->waitForCube(curJob->cube._cube, 0);
			return true;
		} else {
			return true;
		}
		return false;
	}
	bool loadCubeRemoteSmall(LoadData* curJob) {
		if(curJob->cache.empty())
			return false;
		auto r=CacheThread::instance()->waitForFile(curJob->cache[0]);
		if(r>=1)
			return false;
		auto cacheFile=std::move(curJob->cache[r]);
		auto s=loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Empty, CubeData::State::Reading);
		if(s==CubeData::State::Empty) {
			try {
				if(!cacheFile.isReady())
					throwError("Failed to download file");
				std::unique_ptr<ImageReader> imageReader{ImageReader::create(curJob->files[r].toStdString(), cacheFile.buffers(), cacheFile.size())};
				loadImageSmall(imageReader.get(), curJob->files[0], curJob->cube._cube);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Ready);
				curJob->cube->nFinished++;
			} catch(std::exception& e) {
				printMessage("Failed to load image: ", curJob->files[r]);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Error);
				curJob->cube->nFinished++;
				curJob->cube->nError++;
			}
			return true;
		} else if(s==CubeData::State::Reading) {
			loaderShared->waitForCube(curJob->cube._cube, 0);
			return true;
		} else {
			return true;
		}
	}
	bool loadCubeRemoteBig(LoadData* curJob) {
		if(curJob->cache.empty())
			return false;
		auto r=CacheThread::instance()->waitForFiles(curJob->cache);
		if(r>=curJob->cache.size())
			return false;
		auto cacheFile=std::move(curJob->cache[r]);
		auto cpi=curJob->ptrs[r].i;
		auto s=loaderShared->changeState(curJob->cube._cube, cpi, CubeData::State::Empty, CubeData::State::Reading);
		if(s==CubeData::State::Empty) {
			try {
				if(!cacheFile.isReady())
					throwError("Failed to download file");
				std::unique_ptr<ImageReader> imageReader{ImageReader::create(curJob->files[r].toStdString(), cacheFile.buffers(), cacheFile.size())};
				loadImageBig(imageReader.get(), curJob->files[0], curJob->cube._cube, curJob->ptrs[r].xi, curJob->ptrs[r].yi, curJob->ptrs[r].zi, sources[curJob->pos.idx].cubesize[0], sources[curJob->pos.idx].cubesize[1], sources[curJob->pos.idx].cubesize[2]);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Ready);
				curJob->cube->nFinished++;
			} catch(std::exception& e) {
				printMessage("Failed to load image: ", curJob->files[r]);
				loaderShared->changeState(curJob->cube._cube, 0, CubeData::State::Reading, CubeData::State::Error);
				curJob->cube->nFinished++;
				curJob->cube->nError++;
			}
			return true;
		} else if(s==CubeData::State::Reading) {
			loaderShared->waitForCube(curJob->cube._cube, 0);
			return true;
		} else {
			return true;
		}
		return false;
	}

	void loadCubes () {
		std::deque<std::unique_ptr<LoadData>> toloadCur{};

		std::unique_lock<std::mutex> lck{mtx_in};
		while(!m_stopRequested) {
			std::unordered_set<LoadPosition> toloadNew{};
			for(auto& p: m_toload) {
				if(p.second.idx>0)
					toloadNew.insert(p.second);
			}
			lck.unlock();
			{
				std::deque<std::unique_ptr<LoadData>> toloadTmp{};
				std::vector<std::unique_ptr<LoadData>> toloadDel{};
				std::swap(toloadTmp, toloadCur);
				int nLocal=0;
				int nRemote=0;
				for(auto& pos: toloadTmp) {
					auto i=toloadNew.find(pos->pos);
					if(i!=toloadNew.end()) {
						if(sources[pos->pos.idx].location->isUrl) {
							nRemote++;
						} else {
							nLocal++;
						}
						toloadCur.push_back(std::move(pos));
						toloadNew.erase(i);
					} else {
						toloadDel.push_back(std::move(pos));
					}
				}
				for(auto& pos: toloadNew) {
					if(sources[pos.idx].location->isUrl) {
						if(nRemote<2) {
							std::unique_ptr<LoadData> data{};
							if(sources[pos.idx].location->isPat) {
								data=setupJobRemoteBig(pos);
							} else {
								data=setupJobRemoteSmall(pos);
							}
							if(data) {
								toloadCur.push_back(std::move(data));
								nRemote++;
							}
						}
					} else {
						if(nLocal<1) {
							std::unique_ptr<LoadData> data{};
							if(sources[pos.idx].location->isPat) {
								data=setupJobLocalBig(pos);
							} else {
								data=setupJobLocalSmall(pos);
							}
							if(data) {
								toloadCur.push_front(std::move(data));
								nLocal++;
							}
						}
					}
				}
			}

			if(!toloadCur.empty()) {
				auto curJob=toloadCur.front().get();
				auto& source=sources[curJob->pos.idx];

				bool progr=false;
				if(source.location->isUrl) {
					if(source.location->isPat) {
						progr=loadCubeRemoteBig(curJob);
					} else {
						progr=loadCubeRemoteSmall(curJob);
					}
				} else {
					if(source.location->isPat) {
						progr=loadCubeLocalBig(curJob);
					} else {
						progr=loadCubeLocalSmall(curJob);
					}
				}

				lck.lock();
				if(curJob->cube->finished()) {
					QVector<int> chs{};
					auto filesLoadedTmp=0;
					std::vector<decltype(m_toload.begin())> toremove{};
					for(auto it=m_toload.begin(); it!=m_toload.end(); ++it) {
						auto ch=it->first;
						if(it->second==curJob->pos) {
							chs.push_back(ch);
							nFilesLoaded+=it->second.nFiles();
							toremove.push_back(it);
						}
						for(size_t i=1; i<toloadCur.size(); i++) {
							if(toloadCur[i].get()->pos==it->second) {
								filesLoadedTmp+=toloadCur[i]->cube->nFinished;
							}
						}
					}
					for(auto it: toremove)
						m_toload.erase(it);

					bool err=curJob->cube->nError;
					if(!chs.empty()) {
						std::unique_lock<std::mutex> lck_out{mtx_out};
						cubes_out.push_back({curJob->pos.idx, Cube{}});
						if(!err) {
							cubes_out.back().second.data=std::move(curJob->cube);
							cubes_out.back().second.xform=sources[curJob->pos.idx].xform;
						}
						Q_EMIT thread->cubeFinished();
						// msgbox(errror)
					}
					toloadCur.pop_front();
					Q_EMIT thread->updateProgress(nFilesLoaded+filesLoadedTmp, nFilesToLoad);
				} else if(progr) {
					auto filesLoadedTmp=0;
					for(auto it=m_toload.begin(); it!=m_toload.end(); ++it) {
						for(size_t i=0; i<toloadCur.size(); i++) {
							if(toloadCur[i].get()->pos==it->second) {
								filesLoadedTmp+=toloadCur[i].get()->cube->nFinished;
							}
						}
					}
					Q_EMIT thread->updateProgress(nFilesLoaded+filesLoadedTmp, nFilesToLoad);
				}
				continue;
			}
			lck.lock();
			cv_in.wait(lck);
		}
	}
	void setupSources() {
		sources.push_back(Source{QString{}, nullptr, nullptr, {0, 0, 0}, {0, 0, 0}, CubeXform{}});

		for(auto& loc: rootlocs) {
			if(loc.location.compare(QLatin1String{"catalog"}, Qt::CaseInsensitive)==0
					|| loc.location.endsWith(QLatin1String{"/catalog"}, Qt::CaseInsensitive)) {
				if(isUrl(loc.location)) {
					loadCatalog<true>("", loc.location, loc.origin, loc.direction);
				} else {
					loadCatalog<false>("", loc.location, loc.origin, loc.direction);
				}
			} else {
				if(loc.location.endsWith(QLatin1String{".nrrd"}, Qt::CaseInsensitive)
#ifdef WITH_VP9
						|| loc.location.endsWith(QLatin1String{".webm"}, Qt::CaseInsensitive)
#endif
#ifdef WITH_HEVC
						|| loc.location.endsWith(QLatin1String{".hevc"}, Qt::CaseInsensitive)
#endif
						|| loc.location.endsWith(QLatin1String{".tif"}, Qt::CaseInsensitive)
						|| loc.location.endsWith(QLatin1String{".tiff"}, Qt::CaseInsensitive)) {
					Location* location{nullptr};
					QString fn{};
					if(isUrl(loc.location)) {
						location=loaderShared->getLocation(false, true, loc.location);
						fn=QUrl{loc.location}.fileName();
					} else {
						location=loaderShared->getLocation(false, false, loc.location);
						fn=QFileInfo{loc.location}.fileName();
					}
					if(location)
						sources.push_back(Source{fn, location, nullptr, {0, 0, 0}, {0, 0, 0}, {loc.origin, loc.direction}});
				} else {
					throwError("Unknown input volume type.");
				}
			}
		}
		for(auto& s: sources) {
			for(int i=0; i<3; i++)
				s.xform.resolution[i]=sqrt(s.xform.direction[0+i*3]*s.xform.direction[0+i*3]+s.xform.direction[1+i*3]*s.xform.direction[1+i*3]+s.xform.direction[2+i*3]*s.xform.direction[2+i*3]);
			if(!calcInverse(s.xform.direction, s.xform.direction_inv)) {
				for(int i=0; i<9; i++)
					std::cerr<<s.xform.direction[i]<<"\n";
				throwError("Failed to calculate the inverse of direction matrix.");
			}
			if(s.annotTree) {
				try {
					if(!s.annotTree->ensureLoaded())
						s.annotTree=nullptr;
				} catch(const std::exception& e) {
					Q_EMIT thread->threadWarning(QString{"Failed to parse annotation: "}+e.what());
					s.annotTree=nullptr;
				}
			}
		}

		sources_ready=true;
		Q_EMIT thread->sourcesReady();
	}

	LoadThreadPriv(Session* s, LoadThread* _loader):
		session{s}, thread{_loader},
		loaderShared{Tracer::instance()->loaderShared()},
		rootlocs{}, pathsVisited{}, sources{}, sources_ready{false},
		mtx_in{}, cv_in{}, m_stopRequested{false}, m_toload{},
		nFilesToLoad{0}, nFilesLoaded{0}
	{
	}
	LoadThreadPriv(Session* s, LoadThread* _loader, const LoadThreadInput& input):
		LoadThreadPriv{s, _loader}
	{
		if(input.location.isEmpty())
			return;
		if(input.location.compare(QLatin1String{"none"}, Qt::CaseInsensitive)==0)
			return;
		rootlocs.push_back(input);
	}
	void stop() {
		std::unique_lock<std::mutex> lck{mtx_in};
		m_stopRequested=true;
		cv_in.notify_one();
	}
	void cancel() {
		std::unique_lock<std::mutex> lck{mtx_in};
		for(auto& p: m_toload) {
			if(p.second.idx>0) {
				nFilesToLoad-=p.second.nFiles();
			}
		}
		m_toload.clear();
	}
};

// LoadThread
void LoadThread::run() {
	printMessage("Load thread started");
	try {
		priv->setupSources();
		priv->loadCubes();
	} catch(const std::exception& e) {
		Q_EMIT threadError(QString{"Unexpected error in load thread: "}+=e.what());
		printMessage("Load thread error");
	}
	printMessage("Load thread finished");
}
LoadThread::LoadThread(Session* s):
	QThread{s}, priv{new LoadThreadPriv{s, this}}
{
}
LoadThread::LoadThread(Session* s, const LoadThreadInput& input):
	QThread{s}, priv{new LoadThreadPriv{s, this, input}}
{
}
LoadThread::~LoadThread() {
	delete priv;
}

QVector<QString> LoadThread::sources() const {
	if(!priv->sources_ready)
		return {};
	QVector<QString> srcs;
	for(auto& s: priv->sources)
		srcs.push_back(s.name);
	return srcs;
}
bool LoadThread::getBoundingBox(std::array<double, 6>* pbbox) const {
	if(!pbbox)
		return false;
	auto& bbox=*pbbox;

	bool expanded{false};
	for(auto& s: priv->sources) {
		for(int dir=0; dir<3; dir++) {
			double rr=s.xform.origin[dir];
			double ll=rr;
			for(int k=0; k<3; k++) {
				if(s.xform.direction[dir+k*3]>=0) {
					rr+=s.xform.direction[dir+k*3]*s.size[k];
				} else {
					ll+=s.xform.direction[dir+k*3]*s.size[k];
				}
			}
			if(ll<bbox[dir*2+0]) {
				bbox[dir*2+0]=ll;
				expanded=true;
			}
			if(rr>bbox[dir*2+1]) {
				bbox[dir*2+1]=rr;
				expanded=true;
			}
		}
	}
	return expanded;
}
bool LoadThread::getMinVoxelSize(double* vsize) const {
	if(!vsize)
		return false;

	bool expanded{false};
	for(auto& s: priv->sources) {
		for(int dir=0; dir<3; dir++) {
			auto r=s.xform.resolution[dir];
			if(r<*vsize) {
				*vsize=r;
				expanded=true;
			}
		}
	}
	return expanded;
}
std::vector<std::pair<int, Cube>> LoadThread::getOutput() {
	std::vector<std::pair<int, Cube>> ret{};
	std::unique_lock<std::mutex> lck_out{priv->mtx_out};
	std::swap(ret, priv->cubes_out);
	return ret;
}
bool LoadThread::reallyFinished() const {
	size_t n=0;
	{
		std::unique_lock<std::mutex> lck_out{priv->mtx_out};
		n+=priv->cubes_out.size();
		printMessage("N left: ", n);
	}
	{
		std::unique_lock<std::mutex> lck_in{priv->mtx_in};
		for(auto& p: priv->m_toload) {
			if(p.second.idx>0)
				n++;
		}
	}
	printMessage("N left: ", n);
	return n==0;
}

void LoadThread::stop() {
	return priv->stop();
}

bool LoadThread::setPosition(int ch, int idx, double x, double y, double z, double sight, const Cube& cur) {
	return priv->setPosition(ch, idx, x, y, z, sight, cur);
}
void LoadThread::cancel() {
	return priv->cancel();
}








// Helpers
template<typename T, size_t N> bool parseValues(const std::string& s, std::array<T, N>& v) {
	std::istringstream iss{s};
	for(size_t i=0; i<N; i++) {
		iss>>v[i];
		if(!iss)
			return false;
	}
	return true;
}
template<> QString compose<true>(const QString& pos, const QString& path) {
	if(QDir::isAbsolutePath(path)) {
		auto slash1=pos.indexOf('/');
		if(slash1<0)
			return {};
		auto slash2=pos.indexOf('/', slash1+1);
		if(slash2<0)
			return {};
		auto slash3=pos.indexOf('/', slash2+1);
		if(slash3<0)
			return {};
		return pos.left(slash3)+path;
	}
	return pos.left(pos.length()-7)+path;
}
template<> QString compose<false>(const QString& pos, const QString& path) {
	if(QDir::isAbsolutePath(path))
		return path;
	return pos.left(pos.length()-7)+path;
}

